//
//  RSATools.swift
//  Library_y
//
//  Created by JC on 2023/6/6.
//

import Foundation
import Security

private let publicKeyIdentifier = "baseModule.publicKey"
private let privateKeyIdentifier = "baseModule.privateKey"
private let publicKeyTag = publicKeyIdentifier.data(using: .utf8)!
private let privateKeyTag = privateKeyIdentifier.data(using: .utf8)!

enum RSAKeySize: Int {
    case size512 = 512
    case size768 = 768
    case size1024 = 1024
    case size2048 = 2048
}

class RSATools {

    static let shared = RSATools()

    var publicSecKey: SecKey?
    var privateSecKey: SecKey?

    private let keySizeType: RSAKeySize = .size2048
    
    // 生成RSA密钥对
    func generateRSAKeyPair() {
        let keySize = keySizeType.rawValue
        publicSecKey = getRSAKeyFromKeychain(isPrivate: false, keySize: keySize)
        privateSecKey = getRSAKeyFromKeychain(isPrivate: true, keySize: keySize)
        if publicSecKey != nil {
            return
        }
        publicSecKey = nil
        privateSecKey = nil
        let parameters = [kSecAttrKeyType: kSecAttrKeyTypeRSA,
                          kSecAttrKeySizeInBits: keySize] as [CFString : Any]
        let ret = SecKeyGeneratePair(parameters as CFDictionary, &publicSecKey, &privateSecKey)
        assert(ret == errSecSuccess, "Error For SecKeyGeneratePair: \(ret)")

        saveRSAKeyToKeychain(key: publicSecKey!, keySize: keySize, isPrivate: false)
        saveRSAKeyToKeychain(key: privateSecKey!, keySize: keySize, isPrivate: true)
    }

    // 公钥加密
    @objc public func encrypt(source: String) -> String {

        guard !source.isEmpty && self.publicSecKey != nil else {
            return ""
        }
        let data: NSData = (source.data(using: String.Encoding.utf8)! as NSData)
        var error: Unmanaged<CFError>?
        let resData =  SecKeyCreateEncryptedData(self.publicSecKey!, SecKeyAlgorithm.rsaEncryptionPKCS1, data as CFData, &error) as Data?
        if error != nil {
            Log_y.error("res = \(String(describing: error?.takeUnretainedValue().localizedDescription))")
            return ""
        }
        return resData!.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: NSData.Base64EncodingOptions.lineLength64Characters.rawValue))
    }

    // 私钥解密
    @objc public func decrypt(source: String) -> String {

        guard !source.isEmpty && self.privateSecKey != nil else {
            return ""
        }
        //base64 decode
        guard let data: Data = NSData(base64Encoded: (source as String), options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data? else {
            return ""
        }

        var error: Unmanaged<CFError>?
        let resData =  SecKeyCreateDecryptedData(self.privateSecKey!, SecKeyAlgorithm.rsaEncryptionPKCS1, data as CFData, &error) as Data?
        if error != nil {
            Log_y.error("res = \(String(describing: error?.takeUnretainedValue().localizedDescription))")
            return ""
        }
        return String(data: resData!, encoding: String.Encoding.utf8)!
    }

    private func getKeyDataFrom(secKey: SecKey, tag: Data) -> Data {
        var data: Data?

        var query = [String: Any]()
        query[kSecClass as String] = kSecClassKey
        query[kSecAttrApplicationTag as String] = tag
        query[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA

        var attributes = query
        attributes[kSecValueRef as String] = secKey
        attributes[kSecReturnData as String] = true
        var result: CFTypeRef?
        let status = SecItemAdd(attributes as CFDictionary, &result)

        if status == errSecSuccess {
            data = result as? Data
            SecItemDelete(query as CFDictionary)
        }
        return data!
    }
    
    private func saveRSAKeyToKeychain(key: SecKey, keySize: size_t, isPrivate: Bool) {
        var saveDictionary = [String: Any]()
        let keyClass = isPrivate ? kSecAttrKeyClassPrivate : kSecAttrKeyClassPublic
        saveDictionary[kSecClass as String] = kSecClassKey
        saveDictionary[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA
        saveDictionary[kSecAttrApplicationTag as String] = isPrivate ? privateKeyTag : publicKeyTag
        saveDictionary[kSecAttrKeyClass as String] = keyClass
        saveDictionary[kSecValueData as String] = getKeyDataFrom(secKey: key, tag: isPrivate ? privateKeyTag : publicKeyTag)
        saveDictionary[kSecAttrKeySizeInBits as String] = keySize
        saveDictionary[kSecAttrEffectiveKeySize as String] = SecKeyGetBlockSize(key)
        saveDictionary[kSecAttrCanDerive as String] = kCFBooleanFalse
        saveDictionary[kSecAttrCanEncrypt as String] = kCFBooleanTrue
        saveDictionary[kSecAttrCanDecrypt as String] = kCFBooleanTrue
        saveDictionary[kSecAttrCanVerify as String] = kCFBooleanTrue
        saveDictionary[kSecAttrCanSign as String] = kCFBooleanFalse
        saveDictionary[kSecAttrCanWrap as String] = kCFBooleanTrue
        saveDictionary[kSecAttrCanUnwrap as String] = kCFBooleanFalse
        saveDictionary[kSecAttrApplicationLabel as String] = isPrivate ? privateKeyIdentifier : publicKeyIdentifier
        SecItemDelete(saveDictionary as CFDictionary)
        let status = SecItemAdd(saveDictionary as CFDictionary, nil)
        assert(status == errSecSuccess, "SaveRSAKeyToKeychain Failure")
    }

    // swiftlint:disable force_cast
    private func getRSAKeyFromKeychain(isPrivate: Bool, keySize: size_t) -> SecKey? {
        var queryDictionary = [String: Any]()
        let keyClass = isPrivate ? kSecAttrKeyClassPrivate : kSecAttrKeyClassPublic

        queryDictionary[kSecClass as String] = kSecClassKey
        queryDictionary[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA
        queryDictionary[kSecAttrApplicationTag as String] = isPrivate ? privateKeyTag : publicKeyTag
        queryDictionary[kSecAttrKeyClass as String] = keyClass
        queryDictionary[kSecReturnRef as String] = kCFBooleanTrue
        queryDictionary[kSecAttrApplicationLabel as String] =  isPrivate ? privateKeyIdentifier : publicKeyIdentifier
        queryDictionary[kSecAttrKeySizeInBits as String] = keySize

        var key: CFTypeRef?
        let status = SecItemCopyMatching(queryDictionary as CFDictionary, &key)

        if status == errSecSuccess {
            let result = key as! SecKey
            return result
        }
        return nil
    }
    // swiftlint:enable force_cast
}
